package com.jt.resource.aspect;

import com.jt.resource.annotation.RequiredLog;
import com.jt.resource.pojo.Log;
import com.jt.resource.service.RemoteLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Date;


/**
 * 自定义行为日志切面
 * @Aspect 注解描述的类型为一个切面类型, 在此类中可以定义:
 * 1)切入点(切入扩展逻辑的位置~例如权限控制,日志记录,事务处理的位置),在
 * @Aspect描述的类中,通常使用@Pointcut注解进行定义.使用切入点描述的方法为切入点方法.
 * 2)通知方法(在切入点对应的目标方法执行前后要执行逻辑需要写到这样的方法中),在
 * @Aspect描述的类中,通过@Before,@After,@Aroud,@AfterReturning,@AfterThrowing
 * 这样的注解进行描述.
 * a: @Before切入点方法执行之前执行
 * b: @After 切入点方法执行之后执行(不管切入点方法是否执行成功了,它都会执行)
 * c: @Aroud 切入点方法执行之前和之后都可以执行(最重要)
 * d: @AfterReturning 切入点方法成功执行之后执行
 * e: @AfterThrowing 切入点方法执行时出了异常会执行
 */
@Slf4j
@Aspect
@Component
public class LogAspect {
    /**
     * 切入点的定义，采用了注解方式的切入点表达式，也就是说使用
     * RequiredLog注解描述方法时，被描述的方法就是切入点方法，
     * 我们要在这样的方法上进行功增强，这里的doLog方法，只是启动
     * 了承载@Pointcut注解的作用，方法体中不需要写任何内容。
     */
    @Pointcut("@annotation(com.jt.resource.annotation.RequiredLog)")
    public void doLog() {

    }

    /**
     * 通知方法在方法内可以手动调用目标方法执行链，在执行链执行过程中获取
     * 用户行为日志，并进行封装和记录。
     *
     * @return
     * @throws Throwable
     */
    @Around("doLog()")
    public Object doAround(ProceedingJoinPoint jp) throws Throwable{
        long time = -1L;
        int status =1;
        String error = "";
        //获取方法执行时间
        long t1 = System.currentTimeMillis();
        System.out.println("执行开始时间：" + t1);
        try {
        //调用目标执行链，这个执行链的终端是你的切入点方法
        Object result = jp.proceed();
        //获取目标方法执行结束的时间
        long t2 = System.currentTimeMillis();
        System.out.println("执行结束时间：" + t2);
        time = t2-t1;
        return result;
    }catch(Throwable e){
        long t3 = System.currentTimeMillis();
        System.out.println("Exception：" + t3);
        time = t3-t1;
        status = 0;
        error = e.getMessage();
        throw e;
    }finally {
            //日志的记录
            saveLog(jp,time,status, error);
        }
    }

    private void saveLog(ProceedingJoinPoint jp, Long time,int status,String error) throws NoSuchMethodException, IOException {
        //获取用户行为日志
        Class<?> targetClass = jp.getTarget().getClass();
        //获取目标方法
        MethodSignature signature = (MethodSignature)jp.getSignature();
        //获取方法对象
        Method targetMethod = targetClass.getDeclaredMethod(signature.getName(), signature.getParameterTypes());
        //获取方法上的RequiredLog注解内容
        RequiredLog requiredLog = targetMethod.getAnnotation(RequiredLog.class);
        //获取注解中的内容(这个内容为我们定义的操作名)
        String operation = requiredLog.value();
        //获取目标方法名(全类名+目标方法名)
        String targetMethodName = targetClass.getName()+"."+targetMethod.getName();
        //获取目标方法执行时的传入的参数
        String params = new ObjectMapper().writeValueAsString(jp.getArgs());
        //1.获取登录用户名
        String username = (String)SecurityContextHolder.getContext()
                .getAuthentication()
                .getPrincipal();
        //2.获取ip地址(从当前线程获取request对象，然后基于request)获取ip地址)
        ServletRequestAttributes requestAttributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String ip = requestAttributes.getRequest().getRemoteAddr();
        //2.封装用户行为日志(Log)
        Log userLog=new Log();
        //谁
        userLog.setUsername(username);
        userLog.setIp(ip);
        //在什么时间
        userLog.setCreatedTime(new Date());
        //执行了什么操作
        userLog.setOperation(operation);
        //访问了什么方法
        userLog.setMethod(targetMethodName);
        //传递什么参数
        userLog.setParams(params);
        //耗时是多少
        userLog.setTime(time);
        //执行状态以及错误信息
        userLog.setStatus(status);
        userLog.setError(error);
        //3.记录用户行为日志(控制台，文件，数据库)
        log.debug("log {}",userLog);
        remoteLogService.insertLog(userLog);

    }
    /**
     * Feign接口，通过此接口将用户日志对象传递给system工程。
     */
    @Autowired
    private RemoteLogService remoteLogService;
}
